package com.lcz.manage.util;

import com.google.common.base.Preconditions;

import java.io.Serializable;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * Lazy Evaluation
 * @author  luchunzhou
 * @date    2021-08-26
 * @version 1.0
 */
public class SupplierUtils {

    public static <T> Supplier<T> memorize(Supplier<T> delegate) {
        return (delegate instanceof SupplierUtils.MemorizingSupplier)
                ? delegate : new SupplierUtils.MemorizingSupplier<>(Objects.requireNonNull(delegate));
    }

    public static <T> Supplier<T> memoizeWithExpiration(Supplier<T> delegate, long duration, TimeUnit unit) {
        return new SupplierUtils.MemoizeWithExpirationSupplier<>(Objects.requireNonNull(delegate), duration, unit);
    }

    static class MemorizingSupplier<T> implements Supplier<T>, Serializable {

        final Supplier<T> delegate;

        transient volatile boolean initialized;

        transient T value;

        MemorizingSupplier(Supplier<T> delegate) {
            this.delegate = delegate;
        }

        @Override
        public T get() {
            // A 2-field variant of Double Checked Locking.
            if (!initialized) {
                synchronized (this) {
                    if (!initialized) {
                        T t = delegate.get();
                        value = t;
                        initialized = true;
                        return t;
                    }
                }
            }
            return value;
        }

        @Override
        public String toString() {
            return "Suppliers.memoize(" + delegate + ")";
        }

        private static final long serialVersionUID = -2326935930047684867L;
    }


    static class MemoizeWithExpirationSupplier<T> implements Supplier<T>, Serializable {

        final Supplier<T> delegate;

        final long durationNanos;

        transient volatile long expirationNanos;

        transient T value;

        MemoizeWithExpirationSupplier(Supplier<T> delegate, long duration, TimeUnit unit) {
            this.delegate = delegate;
            this.durationNanos = unit.toNanos(duration);
            Preconditions.checkArgument(duration > 0L);
        }

        @Override
        public T get() {
            long nanos = this.expirationNanos;
            long now = System.nanoTime();
            if (nanos == 0L || now - nanos >= 0L) {
                synchronized(this) {
                    if (nanos == this.expirationNanos) {
                        T t = this.delegate.get();
                        this.value = t;
                        nanos = now + this.durationNanos;
                        this.expirationNanos = nanos == 0L ? 1L : nanos;
                        return t;
                    }
                }
            }

            return this.value;
        }

        @Override
        public String toString() {
            String var1 = String.valueOf(String.valueOf(this.delegate));
            long var2 = this.durationNanos;
            return (new StringBuilder(62 + var1.length())).append("Suppliers.memoizeWithExpiration(").append(var1).append(", ").append(var2).append(", NANOS)").toString();
        }

        private static final long serialVersionUID = -2326935930047684564L;
    }
}
